﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using NUnit.Framework;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.Example;
using DomainDriver.DomainModeling.Example.DomainObjects;
using DomainDriver.DomainModeling.Repositories;
using DomainDriver.DomainModeling.StorageManagers;
using DomainDriver.DomainModeling.StorageManagers.DataSetStorage;
using DomainDriver.DomainModeling.StorageManagers.EntityFrameworkStorage;

namespace DomainDriver.DomainModeling.UnitTests
{
    [TestFixture]
    public class ConstructionCodeTests
    {
        [Test]
        public void CreateDatasource()
        {
            //DataSourcePersistenceType persistenceType = DataSourcePersistenceType.Database;
            Type datasourceType = typeof(ExampleModelDatabaseContext);

            if (datasourceType == null)
            { throw new ApplicationException(); }

            ConstructorInfo defaultConstructor = datasourceType.GetConstructor(new Type[] { });
            if (defaultConstructor == null)
            { throw new ApplicationException(); }

            object context = defaultConstructor.Invoke(new object[] { });
            ExampleModelDatabaseContext typedContext = context as ExampleModelDatabaseContext;
        }

        [Test]
        public void CreateRepository()
        {
            Type keyType = typeof(Guid);
            Type domainObjectType = typeof(DummyDomainObject);
            Type genericRepositoryType = typeof(Repository<Guid, DummyDomainObject>);
            Type repositoryType = typeof(Repository<Guid, DummyDomainObject>);
            Type storageManagerType = typeof(IStorageManager<Guid, DummyDomainObject>);

            Type[] constructorArgs = new Type[] { storageManagerType };
            ConstructorInfo constructor = repositoryType.GetConstructor(constructorArgs);

            object repository = constructor.Invoke(new object[] { null });
            Repository<Guid, DummyDomainObject> typedRepository = repository as Repository<Guid, DummyDomainObject>;
        }

        [Test]
        public void CreateRepositoryWithoutGenerics()
        {
            Type keyType = typeof(Guid);
            Type domainObjectType = typeof(DummyDomainObject);
            Type genericRepositoryType = typeof(Repository<,>);
            Type storageManagerType = typeof(IStorageManager<Guid, DummyDomainObject>);

            Type[] genericRepositoryParameters = new Type[] { keyType, domainObjectType };
            Type repositoryType = genericRepositoryType.MakeGenericType(genericRepositoryParameters);

            Type[] constructorArgs = new Type[] { storageManagerType };
            ConstructorInfo constructor = repositoryType.GetConstructor(constructorArgs);

            object repository = constructor.Invoke(new object[] { null });
            Repository<Guid, DummyDomainObject> typedRepository = repository as Repository<Guid, DummyDomainObject>;
        }

        [Test]
        public void CreateRepository_TransientStorage()
        {
            Type keyType = typeof(Guid);
            Type domainObjectType = typeof(DummyDomainObject);
            Type repositoryType = typeof(Repository<Guid, DummyDomainObject>);
            Type storageManagerType = typeof(TransientStorageManager<Guid, DummyDomainObject>);
            Type dataProviderType = typeof(IDataProvider);

            // Construct Storage Manager
            Type[] storageManagerConstructorArgs = new Type[] { dataProviderType };
            ConstructorInfo storageManagerConstructor = storageManagerType.GetConstructor(storageManagerConstructorArgs);

            object storageManager = storageManagerConstructor.Invoke(new object[] { null });
            TransientStorageManager<Guid, DummyDomainObject> typedStorageManager = storageManager as TransientStorageManager<Guid, DummyDomainObject>;

            // Construct Repository
            Type[] repositoryConstructorArgs = new Type[] { storageManagerType };
            ConstructorInfo repositoryConstructor = repositoryType.GetConstructor(repositoryConstructorArgs);

            object repository = repositoryConstructor.Invoke(new object[] { storageManager });
            Repository<Guid, DummyDomainObject> typedRepository = repository as Repository<Guid, DummyDomainObject>;
        }

        [Test]
        public void CreateRepository_DataSetStorage()
        {
            Type dataSourceType = typeof(ExampleModelDataSet);
            Type keyType = typeof(Guid);
            Type domainObjectType = typeof(DummyDomainObject);
            Type repositoryType = typeof(Repository<Guid, DummyDomainObject>);
            Type genericStorageManagerType = typeof(DataSetFileStorageManager<,,>);
            Type dataProviderType = typeof(IDataProvider);
            Type constructionMethodType = typeof(Expression<Func<DummyDomainObject>>);

            // Construct Storage Manager
            Type[] genericStorageManagerParameters = new Type[] { keyType, domainObjectType, dataSourceType };
            Type storageManagerType = genericStorageManagerType.MakeGenericType(genericStorageManagerParameters);

            Type[] storageManagerConstructorArgs = new Type[] { dataProviderType, constructionMethodType };
            ConstructorInfo storageManagerConstructor = storageManagerType.GetConstructor(storageManagerConstructorArgs);

            DataProvider dataProvider = new DataProvider(DataSourcePersistenceType.File, dataSourceType);
            Expression<Func<DummyDomainObject>> constructionMethod = (() => new DummyDomainObject());
            Expression untypedConstructionMethod = constructionMethod;
            object storageManager = storageManagerConstructor.Invoke(new object[] { dataProvider, untypedConstructionMethod });
            DataSetFileStorageManager<Guid, DummyDomainObject, ExampleModelDataSet> typedStorageManager = storageManager as DataSetFileStorageManager<Guid, DummyDomainObject, ExampleModelDataSet>;

            // Construct Repository
            Type[] repositoryConstructorArgs = new Type[] { storageManagerType };
            ConstructorInfo repositoryConstructor = repositoryType.GetConstructor(repositoryConstructorArgs);

            object repository = repositoryConstructor.Invoke(new object[] { storageManager });
            Repository<Guid, DummyDomainObject> typedRepository = repository as Repository<Guid, DummyDomainObject>;
        }

        [Test]
        public void CreateRepository_DbContextStorage()
        {
            Type dataSourceType = typeof(ExampleModelDatabaseContext);
            Type keyType = typeof(Guid);
            Type domainObjectType = typeof(DummyDomainObject);
            Type repositoryType = typeof(Repository<Guid, DummyDomainObject>);
            Type genericStorageManagerType = typeof(EntityFrameworkDatabaseStorageManager<,,,>);
            Type dataProviderType = typeof(IDataProvider);
            Type constructionMethodType = typeof(Func<DummyDomainObject>);

            // Construct Storage Manager
            Type[] genericStorageManagerParameters = new Type[] { keyType, domainObjectType, keyType, dataSourceType };
            Type storageManagerType = genericStorageManagerType.MakeGenericType(genericStorageManagerParameters);

            Type[] storageManagerConstructorArgs = new Type[] { dataProviderType };
            ConstructorInfo storageManagerConstructor = storageManagerType.GetConstructor(storageManagerConstructorArgs);

            DataProvider dataProvider = new DataProvider(DataSourcePersistenceType.File, dataSourceType);
            object storageManager = storageManagerConstructor.Invoke(new object[] { dataProvider });
            EntityFrameworkDatabaseStorageManager<Guid, DummyDomainObject, Guid, ExampleModelDatabaseContext> typedStorageManager = storageManager as EntityFrameworkDatabaseStorageManager<Guid, DummyDomainObject, Guid, ExampleModelDatabaseContext>;

            // Construct Repository
            Type[] repositoryConstructorArgs = new Type[] { storageManagerType };
            ConstructorInfo repositoryConstructor = repositoryType.GetConstructor(repositoryConstructorArgs);

            object repository = repositoryConstructor.Invoke(new object[] { storageManager });
            Repository<Guid, DummyDomainObject> typedRepository = repository as Repository<Guid, DummyDomainObject>;
        }
    }
}